package it.crudity.core.dao.impl;

import it.crudity.core.dao.DaoRepository;
import it.crudity.core.dao.GenericDao;
import it.crudity.core.model.BaseEntity;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

/***
 * Dao repository. Use the instance to get entity daos. All methods are type
 * safe. Generic types are ignored internally
 */
@SuppressWarnings({"unchecked","rawtypes"})
@Repository("daoRepository")
public class DaoRepositoryHibernate implements DaoRepository {
	private static Logger logger = LoggerFactory.getLogger(DaoRepositoryHibernate.class);

	/**
	 * This map contains daos mapped by their domain object class No need for
	 * type check, used internally
	 */
	private Map<Class<?>, GenericDao<?, ?>> daoMapByDomainObjectClass = new HashMap<Class<?>, GenericDao<?, ?>>();

	/**
	 * This map contains daos mapped by their own class No need for type check,
	 * used internally
	 */
	private Map<Class<?>, GenericDao<?, ?>> daoMapByDaoInterfaceClass = new HashMap<Class<?>, GenericDao<?, ?>>();

	@Autowired
	protected SessionFactory sessionFactory;

	/***
	 * {@link Autowired}. Registers created daos.
	 * 
	 * @param list created daos
	 */
	@Autowired(required = false)
	protected void registerDaos(
			List<GenericDao<? extends BaseEntity<? extends Serializable>, ? extends Serializable>> list) {
		for (GenericDao<?, ?> dao : list) {
			Class<?> c = dao.getDomainModelObjectClass();
			daoMapByDomainObjectClass.put(c, dao);

			Class<? extends GenericDao> daoClass = dao.getClass();
			Class<?>[] interfaces = daoClass.getInterfaces();
			for (Class<?> iface : interfaces) {
				if (GenericDao.class.isAssignableFrom(iface))
					daoMapByDaoInterfaceClass.put(iface, dao);
			}
		}
	}

	public <T extends BaseEntity<ID>, ID extends Serializable> GenericDao<T, ID> getDaoForEntityClass(Class<T> clazz) {
		GenericDao<T, ID> dao = (GenericDao<T, ID>) daoMapByDomainObjectClass.get(clazz);

		// can't get to use spring for this... ugly
		Class<ID> idClazz = null;
		if (dao == null) {
			try {
				Type t = clazz;
				do {
					t = ((Class) t).getGenericSuperclass();
				} while (t != null && !(t instanceof ParameterizedType));

				ParameterizedType p = (ParameterizedType) t;
				idClazz = (Class<ID>) p.getActualTypeArguments()[0];
			} catch (Exception e) {
				logger.error("Error looking for Entity ID CLASS.", e);
			}
			dao = new GenericDaoHibernate<T, ID>(clazz, idClazz);
			((GenericDaoHibernate) dao).setSessionFactory(sessionFactory);
			daoMapByDomainObjectClass.put(clazz, dao);
		}
		return dao;
	}

	public <T extends BaseEntity<ID>, ID extends Serializable> GenericDao<T, ID> getDaoForEntityInstance(T object) {
		GenericDao<T, ID> dao = getDaoForEntityClass(object.getClass());
		return dao;
	}

	public <D extends GenericDao<T, ID>, T extends BaseEntity<ID>, ID extends Serializable> D getDaoByDaoInterface(
			Class<D> daoClass) {
		return (D) daoMapByDaoInterfaceClass.get(daoClass);
	}

}
